package com.mtiiot.fstcpserver.fastdfs.core;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

public class StorageClient
{
  public static final Base64 base64 = new Base64('-', '_', '.', 0);
  protected TrackerServer trackerServer;
  protected StorageServer storageServer;
  protected byte errno;

  public StorageClient()
  {
    this.trackerServer = null;
    this.storageServer = null;
  }

  public StorageClient(TrackerServer trackerServer, StorageServer storageServer)
  {
    this.trackerServer = trackerServer;
    this.storageServer = storageServer;
  }

    /**
     * lcy 添加 2016-6-28
     * @return
     */
  public TrackerServer getTrackerServer(){
      return this.trackerServer;
  }

  public StorageServer getStorageServer(){
      return this.storageServer;
  }


  public byte getErrorCode()
  {
    return this.errno;
  }

  public String[] upload_file(String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_file(group_name, local_filename, file_ext_name, meta_list);
  }

  protected String[] upload_file(String group_name, String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    byte cmd = 11;
    return upload_file((byte)11, group_name, local_filename, file_ext_name, meta_list);
  }

  protected String[] upload_file(byte cmd, String group_name, String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    File f = new File(local_filename);
    FileInputStream fis = new FileInputStream(f);
    int nPos;
    if (file_ext_name == null)
    {
      nPos = local_filename.lastIndexOf(46);
      if ((nPos > 0) && (local_filename.length() - nPos <= 7))
      {
        file_ext_name = local_filename.substring(nPos + 1);
      }
    }

    try
    {
      return do_upload_file(cmd, group_name, null, null, file_ext_name, f.length(), new UploadStream(fis, f.length()), meta_list);
    }
    finally
    {
      fis.close();
    }
  }

  public String[] upload_file(byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
  }

  public String[] upload_file(String group_name, byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    return do_upload_file((byte)11, group_name, null, null, file_ext_name, length, new UploadBuff(file_buff, offset, length), meta_list);
  }

  public String[] upload_file(byte[] file_buff, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
  }

  public String[] upload_file(String group_name, byte[] file_buff, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    return do_upload_file((byte)11, group_name, null, null, file_ext_name, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
  }

  public String[] upload_file(String group_name, long file_size, UploadCallback callback, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String master_filename = null;
    String prefix_name = null;

    return do_upload_file((byte)11, group_name, master_filename, prefix_name, file_ext_name, file_size, callback, meta_list);
  }

  public String[] upload_file(String group_name, String master_filename, String prefix_name, String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (master_filename == null) || (master_filename.length() == 0) || (prefix_name == null))
    {
      throw new MyException("invalid arguement");
    }

    File f = new File(local_filename);
    FileInputStream fis = new FileInputStream(f);
    int nPos;
    if (file_ext_name == null)
    {
      nPos = local_filename.lastIndexOf(46);
      if ((nPos > 0) && (local_filename.length() - nPos <= 7))
      {
        file_ext_name = local_filename.substring(nPos + 1);
      }
    }

    try
    {
      return do_upload_file((byte)21, group_name, master_filename, prefix_name, file_ext_name, f.length(), new UploadStream(fis, f.length()), meta_list);
    }
    finally
    {
      fis.close();
    }
  }

  public String[] upload_file(String group_name, String master_filename, String prefix_name, byte[] file_buff, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (master_filename == null) || (master_filename.length() == 0) || (prefix_name == null))
    {
      throw new MyException("invalid arguement");
    }

    return do_upload_file((byte)21, group_name, master_filename, prefix_name, file_ext_name, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
  }

  public String[] upload_file(String group_name, String master_filename, String prefix_name, byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (master_filename == null) || (master_filename.length() == 0) || (prefix_name == null))
    {
      throw new MyException("invalid arguement");
    }

    return do_upload_file((byte)21, group_name, master_filename, prefix_name, file_ext_name, length, new UploadBuff(file_buff, offset, length), meta_list);
  }

  public String[] upload_file(String group_name, String master_filename, String prefix_name, long file_size, UploadCallback callback, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    return do_upload_file((byte)21, group_name, master_filename, prefix_name, file_ext_name, file_size, callback, meta_list);
  }

  public String[] upload_appender_file(String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_appender_file(group_name, local_filename, file_ext_name, meta_list);
  }

  protected String[] upload_appender_file(String group_name, String local_filename, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    byte cmd = 23;
    return upload_file((byte)23, group_name, local_filename, file_ext_name, meta_list);
  }

  public String[] upload_appender_file(byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_appender_file(group_name, file_buff, offset, length, file_ext_name, meta_list);
  }

  public String[] upload_appender_file(String group_name, byte[] file_buff, int offset, int length, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    return do_upload_file((byte)23, group_name, null, null, file_ext_name, length, new UploadBuff(file_buff, offset, length), meta_list);
  }

  public String[] upload_appender_file(byte[] file_buff, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String group_name = null;
    return upload_appender_file(group_name, file_buff, 0, file_buff.length, file_ext_name, meta_list);
  }

  public String[] upload_appender_file(String group_name, byte[] file_buff, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    return do_upload_file((byte)23, group_name, null, null, file_ext_name, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length), meta_list);
  }

  public String[] upload_appender_file(String group_name, long file_size, UploadCallback callback, String file_ext_name, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    String master_filename = null;
    String prefix_name = null;

    return do_upload_file((byte)23, group_name, master_filename, prefix_name, file_ext_name, file_size, callback, meta_list);
  }

  public int append_file(String group_name, String appender_filename, String local_filename)
    throws IOException, MyException
  {
    File f = new File(local_filename);
    FileInputStream fis = new FileInputStream(f);
    try
    {
      return do_append_file(group_name, appender_filename, f.length(), new UploadStream(fis, f.length()));
    }
    finally
    {
      fis.close();
    }
  }

  public int append_file(String group_name, String appender_filename, byte[] file_buff)
    throws IOException, MyException
  {
    return do_append_file(group_name, appender_filename, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
  }

  public int append_file(String group_name, String appender_filename, byte[] file_buff, int offset, int length)
    throws IOException, MyException
  {
    return do_append_file(group_name, appender_filename, length, new UploadBuff(file_buff, offset, length));
  }

  public int append_file(String group_name, String appender_filename, long file_size, UploadCallback callback)
    throws IOException, MyException
  {
    return do_append_file(group_name, appender_filename, file_size, callback);
  }

  public int modify_file(String group_name, String appender_filename, long file_offset, String local_filename)
    throws IOException, MyException
  {
    File f = new File(local_filename);
    FileInputStream fis = new FileInputStream(f);
    try
    {
      return do_modify_file(group_name, appender_filename, file_offset, f.length(), new UploadStream(fis, f.length()));
    }
    finally
    {
      fis.close();
    }
  }

  public int modify_file(String group_name, String appender_filename, long file_offset, byte[] file_buff)
    throws IOException, MyException
  {
    return do_modify_file(group_name, appender_filename, file_offset, file_buff.length, new UploadBuff(file_buff, 0, file_buff.length));
  }

  public int modify_file(String group_name, String appender_filename, long file_offset, byte[] file_buff, int buffer_offset, int buffer_length)
    throws IOException, MyException
  {
    return do_modify_file(group_name, appender_filename, file_offset, buffer_length, new UploadBuff(file_buff, buffer_offset, buffer_length));
  }

  public int modify_file(String group_name, String appender_filename, long file_offset, long modify_size, UploadCallback callback)
    throws IOException, MyException
  {
    return do_modify_file(group_name, appender_filename, file_offset, modify_size, callback);
  }

  protected String[] do_upload_file(byte cmd, String group_name, String master_filename, String prefix_name, String file_ext_name, long file_size, UploadCallback callback, NameValuePair[] meta_list)
    throws IOException, MyException
  {
    boolean bUploadSlave = (group_name != null) && (group_name.length() > 0) && (master_filename != null) && (master_filename.length() > 0) && (prefix_name != null);
    boolean bNewConnection;
    if (bUploadSlave)
    {
      bNewConnection = newUpdatableStorageConnection(group_name, master_filename);
    }
    else
    {
      bNewConnection = newWritableStorageConnection(group_name);
    }

    try
    {
      Socket storageSocket = this.storageServer.getSocket();

      byte[] ext_name_bs = new byte[6];
      Arrays.fill(ext_name_bs, (byte)0);
      if ((file_ext_name != null) && (file_ext_name.length() > 0))
      {
        byte[] bs = file_ext_name.getBytes(ClientGlobal.g_charset);
        int ext_name_len = bs.length;
        if (ext_name_len > 6)
        {
          ext_name_len = 6;
        }
        System.arraycopy(bs, 0, ext_name_bs, 0, ext_name_len);
      }
      int offset;
      byte[] masterFilenameBytes;
      byte[] sizeBytes;
      long body_len;
      if (bUploadSlave)
      {
        masterFilenameBytes = master_filename.getBytes(ClientGlobal.g_charset);

        sizeBytes = new byte[16];
        body_len = sizeBytes.length + 16 + 6 + masterFilenameBytes.length + file_size;

        byte[] hexLenBytes = ProtoCommon.long2buff(master_filename.length());
        System.arraycopy(hexLenBytes, 0, sizeBytes, 0, hexLenBytes.length);
        offset = hexLenBytes.length;
      }
      else
      {
        masterFilenameBytes = null;
        sizeBytes = new byte[9];
        body_len = sizeBytes.length + 6 + file_size;

        sizeBytes[0] = ((byte)this.storageServer.getStorePathIndex());
        offset = 1;
      }

      byte[] hexLenBytes = ProtoCommon.long2buff(file_size);
      System.arraycopy(hexLenBytes, 0, sizeBytes, offset, hexLenBytes.length);

      OutputStream out = storageSocket.getOutputStream();
      byte[] header = ProtoCommon.packHeader(cmd, body_len, (byte)0);
      byte[] wholePkg = new byte[(int)(header.length + body_len - file_size)];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
      offset = header.length + sizeBytes.length;
      byte[] prefix_name_bs;
      if (bUploadSlave)
      {
        prefix_name_bs = new byte[16];
        byte[] bs = prefix_name.getBytes(ClientGlobal.g_charset);
        int prefix_name_len = bs.length;
        Arrays.fill(prefix_name_bs, (byte)0);
        if (prefix_name_len > 16)
        {
          prefix_name_len = 16;
        }
        if (prefix_name_len > 0)
        {
          System.arraycopy(bs, 0, prefix_name_bs, 0, prefix_name_len);
        }

        System.arraycopy(prefix_name_bs, 0, wholePkg, offset, prefix_name_bs.length);
        offset += prefix_name_bs.length;
      }

      System.arraycopy(ext_name_bs, 0, wholePkg, offset, ext_name_bs.length);
      offset += ext_name_bs.length;

      if (bUploadSlave)
      {
        System.arraycopy(masterFilenameBytes, 0, wholePkg, offset, masterFilenameBytes.length);
        offset += masterFilenameBytes.length;
      }

      out.write(wholePkg);

      if ((this.errno = (byte)callback.send(out)) != 0)
      {
        return null;
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      if (pkgInfo.body.length <= 16)
      {
        throw new MyException("body length: " + pkgInfo.body.length + " <= " + 16);
      }

      String new_group_name = new String(pkgInfo.body, 0, 16).trim();
      String remote_filename = new String(pkgInfo.body, 16, pkgInfo.body.length - 16);
      String[] results = new String[2];
      results[0] = new_group_name;
      results[1] = remote_filename;

      if ((meta_list == null) || (meta_list.length == 0))
      {
        return results;
      }

      int result = 0;
      try
      {
        result = set_metadata(new_group_name, remote_filename, meta_list, (byte)79);
      }
      catch (IOException ex)
      {
        result = 5;
        throw (ex);
      }
      finally
      {
        if (result != 0)
        {
          this.errno = ((byte)result);
          delete_file(new_group_name, remote_filename);
          return null;
        }
      }

      return results;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  protected int do_append_file(String group_name, String appender_filename, long file_size, UploadCallback callback)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (appender_filename == null) || (appender_filename.length() == 0))
    {
      this.errno = 22;
      return this.errno;
    }

    boolean bNewConnection = newUpdatableStorageConnection(group_name, appender_filename);
    try
    {
      Socket storageSocket = this.storageServer.getSocket();

      byte[] appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
      long body_len = 16 + appenderFilenameBytes.length + file_size;

      byte[] header = ProtoCommon.packHeader((byte)24, body_len, (byte)0);
      byte[] wholePkg = new byte[(int)(header.length + body_len - file_size)];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      int offset = header.length;

      byte[] hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      hexLenBytes = ProtoCommon.long2buff(file_size);
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      OutputStream out = storageSocket.getOutputStream();

      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
      offset += appenderFilenameBytes.length;

      out.write(wholePkg);
      if ((this.errno = (byte)callback.send(out)) != 0)
      {
        return this.errno;
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 0L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return this.errno;
      }

      return 0;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  protected int do_modify_file(String group_name, String appender_filename, long file_offset, long modify_size, UploadCallback callback)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (appender_filename == null) || (appender_filename.length() == 0))
    {
      this.errno = 22;
      return this.errno;
    }

    boolean bNewConnection = newUpdatableStorageConnection(group_name, appender_filename);
    try
    {
      Socket storageSocket = this.storageServer.getSocket();

      byte[] appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
      long body_len = 24 + appenderFilenameBytes.length + modify_size;

      byte[] header = ProtoCommon.packHeader((byte)34, body_len, (byte)0);
      byte[] wholePkg = new byte[(int)(header.length + body_len - modify_size)];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      int offset = header.length;

      byte[] hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      hexLenBytes = ProtoCommon.long2buff(file_offset);
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      hexLenBytes = ProtoCommon.long2buff(modify_size);
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      OutputStream out = storageSocket.getOutputStream();

      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
      offset += appenderFilenameBytes.length;

      out.write(wholePkg);
      if ((this.errno = (byte)callback.send(out)) != 0)
      {
        return this.errno;
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 0L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return this.errno;
      }

      return 0;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public int delete_file(String group_name, String remote_filename)
    throws IOException, MyException
  {
    boolean bNewConnection = newUpdatableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      send_package((byte)12, group_name, remote_filename);
      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 0L);

      this.errno = pkgInfo.errno;
      return pkgInfo.errno;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public int truncate_file(String group_name, String appender_filename)
    throws IOException, MyException
  {
    long truncated_file_size = 0L;
    return truncate_file(group_name, appender_filename, 0L);
  }

  public int truncate_file(String group_name, String appender_filename, long truncated_file_size)
    throws IOException, MyException
  {
    if ((group_name == null) || (group_name.length() == 0) || (appender_filename == null) || (appender_filename.length() == 0))
    {
      this.errno = 22;
      return this.errno;
    }

    boolean bNewConnection = newUpdatableStorageConnection(group_name, appender_filename);
    try
    {
      Socket storageSocket = this.storageServer.getSocket();

      byte[] appenderFilenameBytes = appender_filename.getBytes(ClientGlobal.g_charset);
      int body_len = 16 + appenderFilenameBytes.length;

      byte[] header = ProtoCommon.packHeader((byte)36, body_len, (byte)0);
      byte[] wholePkg = new byte[header.length + body_len];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      int offset = header.length;

      byte[] hexLenBytes = ProtoCommon.long2buff(appender_filename.length());
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      hexLenBytes = ProtoCommon.long2buff(truncated_file_size);
      System.arraycopy(hexLenBytes, 0, wholePkg, offset, hexLenBytes.length);
      offset += hexLenBytes.length;

      OutputStream out = storageSocket.getOutputStream();

      System.arraycopy(appenderFilenameBytes, 0, wholePkg, offset, appenderFilenameBytes.length);
      offset += appenderFilenameBytes.length;

      out.write(wholePkg);
      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 0L);

      this.errno = pkgInfo.errno;
      return pkgInfo.errno;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public byte[] download_file(String group_name, String remote_filename)
    throws IOException, MyException
  {
    long file_offset = 0L;
    long download_bytes = 0L;

    return download_file(group_name, remote_filename, 0L, 0L);
  }

  public byte[] download_file(String group_name, String remote_filename, long file_offset, long download_bytes)
    throws IOException, MyException
  {
    boolean bNewConnection = newReadableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      send_download_package(group_name, remote_filename, file_offset, download_bytes);
      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      byte[] arrayOfByte;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      return pkgInfo.body;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public int download_file(String group_name, String remote_filename, String local_filename)
    throws IOException, MyException
  {
    long file_offset = 0L;
    long download_bytes = 0L;
    return download_file(group_name, remote_filename, 0L, 0L, local_filename);
  }

  public int download_file(String group_name, String remote_filename, long file_offset, long download_bytes, String local_filename)
    throws IOException, MyException
  {
    boolean bNewConnection = newReadableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      FileOutputStream out = new FileOutputStream(local_filename);
      try
      {
        this.errno = 0;
        send_download_package(group_name, remote_filename, file_offset, download_bytes);

        InputStream in = storageSocket.getInputStream();
        ProtoCommon.RecvHeaderInfo header = ProtoCommon.recvHeader(in, (byte)100, -1L);
        this.errno = header.errno;
        if (header.errno != 0)
        {
          int i = header.errno;

          out.close();
          if (this.errno != 0)
          {
            new File(local_filename).delete();
          }

          return i;
        }
        byte[] buff = new byte[262144];
        long remainBytes = header.body_len;

        while (remainBytes > 0L)
        {
          int bytes;
          if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int)remainBytes)) < 0)
          {
            throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
          }

          out.write(buff, 0, bytes);
          remainBytes -= bytes;
        }

        int j = 0;

        out.close();
        if (this.errno != 0)
        {
          new File(local_filename).delete();
        }

        return j;
      }
      catch (IOException ex)
      {
        if (this.errno == 0)
        {
          this.errno = 5;
        }

        throw ex;
      }
      finally
      {
        out.close();
        if (this.errno != 0)
        {
          new File(local_filename).delete();
        }
      }
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public int download_file(String group_name, String remote_filename, DownloadCallback callback)
    throws IOException, MyException
  {
    long file_offset = 0L;
    long download_bytes = 0L;
    return download_file(group_name, remote_filename, 0L, 0L, callback);
  }

  public int download_file(String group_name, String remote_filename, long file_offset, long download_bytes, DownloadCallback callback)
    throws IOException, MyException
  {
    boolean bNewConnection = newReadableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      send_download_package(group_name, remote_filename, file_offset, download_bytes);

      InputStream in = storageSocket.getInputStream();
      ProtoCommon.RecvHeaderInfo header = ProtoCommon.recvHeader(in, (byte)100, -1L);
      this.errno = header.errno;
      if (header.errno != 0)
      {
        return header.errno;
      }

      byte[] buff = new byte[2048];
      long remainBytes = header.body_len;
      int j;
      while (remainBytes > 0L)
      {
        int bytes;
        if ((bytes = in.read(buff, 0, remainBytes > buff.length ? buff.length : (int)remainBytes)) < 0)
        {
          throw new IOException("recv package size " + (header.body_len - remainBytes) + " != " + header.body_len);
        }
        int result;
        if ((result = callback.recv(header.body_len, buff, bytes)) != 0)
        {
          this.errno = ((byte)result);
          return result;
        }

        remainBytes -= bytes;
      }

      return 0;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public NameValuePair[] get_metadata(String group_name, String remote_filename)
    throws IOException, MyException
  {
    boolean bNewConnection = newUpdatableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      send_package((byte)15, group_name, remote_filename);
      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, -1L);

      this.errno = pkgInfo.errno;
      NameValuePair[] arrayOfNameValuePair;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      return ProtoCommon.split_metadata(new String(pkgInfo.body, ClientGlobal.g_charset));
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public int set_metadata(String group_name, String remote_filename, NameValuePair[] meta_list, byte op_flag)
    throws IOException, MyException
  {
    boolean bNewConnection = newUpdatableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      byte[] meta_buff;
      if (meta_list == null)
      {
        meta_buff = new byte[0];
      }
      else
      {
        meta_buff = ProtoCommon.pack_metadata(meta_list).getBytes(ClientGlobal.g_charset);
      }

      byte[] filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
      byte[] sizeBytes = new byte[16];
      Arrays.fill(sizeBytes, (byte)0);

      byte[] bs = ProtoCommon.long2buff(filenameBytes.length);
      System.arraycopy(bs, 0, sizeBytes, 0, bs.length);
      bs = ProtoCommon.long2buff(meta_buff.length);
      System.arraycopy(bs, 0, sizeBytes, 8, bs.length);

      byte[] groupBytes = new byte[16];
      bs = group_name.getBytes(ClientGlobal.g_charset);

      Arrays.fill(groupBytes, (byte)0);
      int groupLen;
      if (bs.length <= groupBytes.length)
      {
        groupLen = bs.length;
      }
      else
      {
        groupLen = groupBytes.length;
      }
      System.arraycopy(bs, 0, groupBytes, 0, groupLen);

      byte[] header = ProtoCommon.packHeader((byte)13, 17 + groupBytes.length + filenameBytes.length + meta_buff.length, (byte)0);

      OutputStream out = storageSocket.getOutputStream();
      byte[] wholePkg = new byte[header.length + sizeBytes.length + 1 + groupBytes.length + filenameBytes.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(sizeBytes, 0, wholePkg, header.length, sizeBytes.length);
      wholePkg[(header.length + sizeBytes.length)] = op_flag;
      System.arraycopy(groupBytes, 0, wholePkg, header.length + sizeBytes.length + 1, groupBytes.length);
      System.arraycopy(filenameBytes, 0, wholePkg, header.length + sizeBytes.length + 1 + groupBytes.length, filenameBytes.length);
      out.write(wholePkg);
      if (meta_buff.length > 0)
      {
        out.write(meta_buff);
      }

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 0L);

      this.errno = pkgInfo.errno;
      return pkgInfo.errno;
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  public FileInfo get_file_info(String group_name, String remote_filename)
    throws IOException, MyException
  {
    if (remote_filename.length() < 44)
    {
      this.errno = 22;
      return null;
    }

    byte[] buff = base64.decodeAuto(remote_filename.substring(10, 37));

    long file_size = ProtoCommon.buff2long(buff, 8);
    if ((remote_filename.length() > 60L) || ((remote_filename.length() > 44L) && ((file_size & 0x0) == 0L)) || ((file_size & 0x0) != 0L))
    {
      FileInfo fi = query_file_info(group_name, remote_filename);
      if (fi == null)
      {
        return null;
      }
      return fi;
    }

    FileInfo fileInfo = new FileInfo(file_size, 0, 0, ProtoCommon.getIpAddress(buff, 0));
    fileInfo.setCreateTimestamp(ProtoCommon.buff2int(buff, 4));
    if (file_size >> 63 != 0L)
    {
      file_size &= 4294967295L;
      fileInfo.setFileSize(file_size);
    }
    fileInfo.setCrc32(ProtoCommon.buff2int(buff, 16));

    return fileInfo;
  }

  public FileInfo query_file_info(String group_name, String remote_filename)
    throws IOException, MyException
  {
    boolean bNewConnection = newUpdatableStorageConnection(group_name, remote_filename);
    Socket storageSocket = this.storageServer.getSocket();
    try
    {
      byte[] filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);
      byte[] groupBytes = new byte[16];
      byte[] bs = group_name.getBytes(ClientGlobal.g_charset);

      Arrays.fill(groupBytes, (byte)0);
      int groupLen;
      if (bs.length <= groupBytes.length)
      {
        groupLen = bs.length;
      }
      else
      {
        groupLen = groupBytes.length;
      }
      System.arraycopy(bs, 0, groupBytes, 0, groupLen);

      byte[] header = ProtoCommon.packHeader((byte)22, groupBytes.length + filenameBytes.length, (byte)0);

      OutputStream out = storageSocket.getOutputStream();
      byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
      System.arraycopy(header, 0, wholePkg, 0, header.length);
      System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
      System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
      out.write(wholePkg);

      ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(storageSocket.getInputStream(), (byte)100, 40L);

      this.errno = pkgInfo.errno;
      if (pkgInfo.errno != 0)
      {
        return null;
      }

      long file_size = ProtoCommon.buff2long(pkgInfo.body, 0);
      int create_timestamp = (int)ProtoCommon.buff2long(pkgInfo.body, 8);
      int crc32 = (int)ProtoCommon.buff2long(pkgInfo.body, 16);
      String source_ip_addr = new String(pkgInfo.body, 24, 16).trim();
      return new FileInfo(file_size, create_timestamp, crc32, source_ip_addr);
    }
    catch (IOException ex)
    {
      if (!bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }

      throw ex;
    }
    finally
    {
      if (bNewConnection)
      {
        try
        {
          this.storageServer.close();
        }
        catch (IOException ex1)
        {
          ex1.printStackTrace();
        }
        finally
        {
          this.storageServer = null;
        }
      }
    }
  }

  protected boolean newWritableStorageConnection(String group_name)
    throws IOException, MyException
  {
    if (this.storageServer != null)
    {
      return false;
    }

    TrackerClient tracker = new TrackerClient();
    this.storageServer = tracker.getStoreStorage(this.trackerServer, group_name);
    if (this.storageServer == null)
    {
      throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
    }
    return true;
  }

  protected boolean newReadableStorageConnection(String group_name, String remote_filename)
    throws IOException, MyException
  {
    if (this.storageServer != null)
    {
      return false;
    }

    TrackerClient tracker = new TrackerClient();
    this.storageServer = tracker.getFetchStorage(this.trackerServer, group_name, remote_filename);
    if (this.storageServer == null)
    {
      throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
    }
    return true;
  }

  protected boolean newUpdatableStorageConnection(String group_name, String remote_filename)
    throws IOException, MyException
  {
    if (this.storageServer != null)
    {
      return false;
    }

    TrackerClient tracker = new TrackerClient();
    this.storageServer = tracker.getUpdateStorage(this.trackerServer, group_name, remote_filename);
    if (this.storageServer == null)
    {
      throw new MyException("getStoreStorage fail, errno code: " + tracker.getErrorCode());
    }
    return true;
  }

  protected void send_package(byte cmd, String group_name, String remote_filename)
    throws IOException
  {
    byte[] groupBytes = new byte[16];
    byte[] bs = group_name.getBytes(ClientGlobal.g_charset);
    byte[] filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);

    Arrays.fill(groupBytes, (byte)0);
    int groupLen;
    if (bs.length <= groupBytes.length)
    {
      groupLen = bs.length;
    }
    else
    {
      groupLen = groupBytes.length;
    }
    System.arraycopy(bs, 0, groupBytes, 0, groupLen);

    byte[] header = ProtoCommon.packHeader(cmd, groupBytes.length + filenameBytes.length, (byte)0);
    byte[] wholePkg = new byte[header.length + groupBytes.length + filenameBytes.length];
    System.arraycopy(header, 0, wholePkg, 0, header.length);
    System.arraycopy(groupBytes, 0, wholePkg, header.length, groupBytes.length);
    System.arraycopy(filenameBytes, 0, wholePkg, header.length + groupBytes.length, filenameBytes.length);
    this.storageServer.getSocket().getOutputStream().write(wholePkg);
  }

  protected void send_download_package(String group_name, String remote_filename, long file_offset, long download_bytes)
    throws IOException
  {
    byte[] bsOffset = ProtoCommon.long2buff(file_offset);
    byte[] bsDownBytes = ProtoCommon.long2buff(download_bytes);
    byte[] groupBytes = new byte[16];
    byte[] bs = group_name.getBytes(ClientGlobal.g_charset);
    byte[] filenameBytes = remote_filename.getBytes(ClientGlobal.g_charset);

    Arrays.fill(groupBytes, (byte)0);
    int groupLen;
    if (bs.length <= groupBytes.length)
    {
      groupLen = bs.length;
    }
    else
    {
      groupLen = groupBytes.length;
    }
    System.arraycopy(bs, 0, groupBytes, 0, groupLen);

    byte[] header = ProtoCommon.packHeader((byte)14, bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length, (byte)0);

    byte[] wholePkg = new byte[header.length + bsOffset.length + bsDownBytes.length + groupBytes.length + filenameBytes.length];
    System.arraycopy(header, 0, wholePkg, 0, header.length);
    System.arraycopy(bsOffset, 0, wholePkg, header.length, bsOffset.length);
    System.arraycopy(bsDownBytes, 0, wholePkg, header.length + bsOffset.length, bsDownBytes.length);
    System.arraycopy(groupBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length, groupBytes.length);
    System.arraycopy(filenameBytes, 0, wholePkg, header.length + bsOffset.length + bsDownBytes.length + groupBytes.length, filenameBytes.length);
    this.storageServer.getSocket().getOutputStream().write(wholePkg);
  }

  public static class UploadBuff
    implements UploadCallback
  {
    private byte[] fileBuff;
    private int offset;
    private int length;

    public UploadBuff(byte[] fileBuff, int offset, int length)
    {
      this.fileBuff = fileBuff;
      this.offset = offset;
      this.length = length;
    }

    public int send(OutputStream out)
      throws IOException
    {
      out.write(this.fileBuff, this.offset, this.length);

      return 0;
    }
  }
}